/*********************************************************************
 * Software License Agreement (Modified BSD License)
 *
 * Copyright (c) 2010, Daniel Munoz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the copyright holders' organizations nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************/

#include <m3n/parametric_m3n.h>

using namespace std;

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
ParametricM3N::ParametricM3N(std::string directory,
                             std::string basename) :
  M3NModel(vector<double> ())
{
  if (loadFromFile(directory, basename) < 0)
  {
    cerr << "ParametricM3N Could not load from file" << endl;
    throw;
  }
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
ParametricM3N::ParametricM3N(const std::vector<double>& robust_potts_params,
                             const double regularization) :
  M3NModel(robust_potts_params), m_regularization(regularization)
{
  if (regularization < 0.0)
  {
    cerr << "ParametricM3N regularization must be non-negative" << endl;
    throw;
  }
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
void ParametricM3N::clear()
{
  m_parameters.clear();
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
void ParametricM3N::doSubgradientUpdate(const std::vector<const RandomField*>& training_rfs,
                                        const bool infer_random,
                                        const double step_size,
                                        M3NLogger& logger)
{
  // ---------------------------------------------------
  if (m_parameters.size() == 0)
  {
    m_parameters.assign(m_total_stack_feature_dim, 0.0);
  }

  // ---------------------------------------------------
  // Compute losses (target values for each sample)
  LinearRegression lin_reg;
  lin_reg.setStackedFeatureDim(m_total_stack_feature_dim);
  structLossResiduals(training_rfs, infer_random, lin_reg, logger);
  const map<const vector<double>*, map<unsigned int, double> >& map_total = lin_reg.getTargets();

  // ---------------------------------------------------
  // Compute negative subgradient over all samples
  vector<double> loss_neg_subgradient(m_total_stack_feature_dim, 0.0);
  map<const vector<double>*, map<unsigned int, double> >::const_iterator iter_features;
  for (iter_features = map_total.begin(); iter_features != map_total.end() ; iter_features++)
  {
    const vector<double>& curr_features = *(iter_features->first);
    size_t feat_dim = curr_features.size();

    const map<unsigned int, double>& map_idx_target = iter_features->second;
    map<unsigned int, double>::const_iterator iter_targets;
    for (iter_targets = map_idx_target.begin(); iter_targets != map_idx_target.end() ; iter_targets++)
    {
      size_t curr_idx = iter_targets->first;
      double curr_target = iter_targets->second;

      for (size_t d = 0 ; d < feat_dim ; d++)
      {
        loss_neg_subgradient[curr_idx + d] += curr_target * (curr_features[d]);
      }
    }
  }

  // ---------------------------------------------------
  // Update
  size_t clique_dim_start = m_node_feature_dim * m_training_labels.size();
  for (size_t d = 0 ; d < m_total_stack_feature_dim ; d++)
  {
    // w_{t+1} = w_t - alpha_t g_t
    // w_{t+1} = w_t + alpha_t (-g_t)
    m_parameters[d] += step_size * (-m_regularization * m_parameters[d] + loss_neg_subgradient[d]);

    // Project clique dims to satisfy submodularity (ASSUMES NON-NEGATIVE FEATURE VALUES)
    if (d >= clique_dim_start)
    {
      m_parameters[d] = max(m_parameters[d], 0.0);
    }
  }
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
int ParametricM3N::computePotential(const RandomField::Node& node,
                                    const unsigned int label,
                                    const size_t max_nbr_regressors,
                                    double& potential_val) const
{
  const vector<double>& features = node.getFeatureVals();
  size_t start_idx = m_node_stacked_feature_start_idx.find(label)->second;
  size_t feat_dim = features.size();
  potential_val = 0.0;
  for (size_t d = 0 ; d < feat_dim ; d++)
  {
    potential_val += (m_parameters[start_idx + d] * features[d]);
  }
  return 0;
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
int ParametricM3N::computePotential(const RandomField::Clique& clique,
                                    const unsigned int clique_set_idx,
                                    const unsigned int label,
                                    const size_t max_nbr_regressors,
                                    double& potential_val) const
{
  const vector<double>& features = clique.getFeatureVals();
  const size_t start_idx =
      m_clique_set_stacked_feature_start_idx[clique_set_idx].find(label)->second;
  size_t feat_dim = features.size();
  potential_val = 0.0;
  for (size_t d = 0 ; d < feat_dim ; d++)
  {
    potential_val += (m_parameters[start_idx + d] * features[d]);
  }
  return 0;
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
int ParametricM3N::doLoadFromFile(ifstream& infile,
                                  string directory,
                                  string basename)
{
  infile >> m_regularization;
  m_parameters.assign(m_total_stack_feature_dim, 0.0);
  for (size_t d = 0 ; d < m_total_stack_feature_dim ; d++)
  {
    infile >> m_parameters[d];
  }
  return 0;
}

// --------------------------------------------------------------
/* See function definition */
// --------------------------------------------------------------
int ParametricM3N::doSaveToFile(ofstream& outfile,
                                string directory,
                                string basename)
{
  outfile << m_regularization << endl;
  for (size_t d = 0 ; d < m_total_stack_feature_dim ; d++)
  {
    outfile << m_parameters[d];
  }
  return 0;
}
